package zdream.pmfield.data.foe;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author Zdream
 * @date 2023-11-17
 * @since 0.0.1
 */
public class MoveAdditionalEffects {
	public static final String E_APPLY_BUFF = "apply-buff";
	public static final String E_DELAY = "delay";
	public static final String E_PARALYSIS = "paralysis";
	public static final String E_BURN = "burn";
	public static final String E_FIXED = "fixed";
	public static final String E_FIXED_BUFF = "fixed-buff";
	public static final String E_MOVE_TO = "moveTo";
	public static final String E_POISONED = "poisoned";
	public static final String E_FROSTBITE = "frostbite";
	public static final String E_FLINCH = "flinch";
	public static final String E_BOUND = "bound";
	public static final String E_ACID = "acid";
	public static final String E_WET = "wet";
	public static final String E_FLYING = "flying";
	public static final String E_DROWSY = "drowsy";
	public static final String E_REMOVE_BUFF = "remove-buff";
	public static final String E_CONFUSED = "confused";
	public static final String E_BSTAT_CHANGE = "b-stat-change";
	public static final String E_BSTAT_SET = "b-stat-set";
	public static final String E_SPLINTERS = "splinters";
	public static final String E_CHARGING = "charging";
	public static final String E_FIXATED = "fixated";

	// tags
	public static final String T_CHARGING = "charging";

	// abilities
	public static final String A_DRAGON_GROUP = "dragon-group";
	public static final String A_BUG_GROUP = "bug-group";
	public static final String A_BIRD_GROUP = "bird-group";
	public static final String A_GHOST_GROUP = "ghost-group";

	private static final Set<String> VALID_EFFECT_TEXTS = new HashSet<>();
	private static final Map<String, Function<String[], Map<String, ?>>> EFFECT_CHECKER = new HashMap<>();

	public static final String K_FIXED_BUFF_ATTACKER_BUFFS = "attacker-buff";
	public static final String K_FIXED_BUFF_DEFENSE_BUFFS = "defense-buff";
	public static final String K_FIXED_BUFF_TYPE = "type";
	public static final String K_FIXED_BUFF_RATE = "rate";

	public static final String K_REMOVE_BUFF_BUFFS = "buffs";
	public static final String K_REMOVE_BUFF_TARGET = "target";

	public static final String K_APPLY_BUFF_BUFF = "buff";
	public static final String K_APPLY_BUFF_TARGET = "target";
	public static final String K_APPLY_BUFF_PROPERTIES = "properties";

	public static final String K_BSTAT_CHANGE_CHANGE = "change";
	public static final String K_BSTAT_CHANGE_TARGET = "target";

	static {
		VALID_EFFECT_TEXTS.add(E_DELAY);
		VALID_EFFECT_TEXTS.add(E_PARALYSIS);
		VALID_EFFECT_TEXTS.add(E_BURN);
		VALID_EFFECT_TEXTS.add(E_MOVE_TO);
		VALID_EFFECT_TEXTS.add(E_POISONED);
		VALID_EFFECT_TEXTS.add(E_FROSTBITE);
		VALID_EFFECT_TEXTS.add(E_FLINCH);
		VALID_EFFECT_TEXTS.add(E_BOUND);
		VALID_EFFECT_TEXTS.add(E_ACID);
		VALID_EFFECT_TEXTS.add(E_WET);
		VALID_EFFECT_TEXTS.add(E_FLYING);
		VALID_EFFECT_TEXTS.add(E_DROWSY);

		EFFECT_CHECKER.put(E_FIXED, MoveAdditionalEffects::parseFixedEffect);
		EFFECT_CHECKER.put(E_FIXED_BUFF, MoveAdditionalEffects::parseFixedBuffEffect);
		EFFECT_CHECKER.put(E_REMOVE_BUFF, MoveAdditionalEffects::parseRemoveBuffEffect);
		EFFECT_CHECKER.put(E_APPLY_BUFF, MoveAdditionalEffects::parseApplyBuffEffect);
		EFFECT_CHECKER.put(E_BSTAT_CHANGE, MoveAdditionalEffects::parseBStatChangeEffect);
		EFFECT_CHECKER.put(E_BSTAT_SET, MoveAdditionalEffects::parseBStatChangeEffect);
		EFFECT_CHECKER.put(E_CHARGING, MoveAdditionalEffects::parsePairProperties);
	}

	public static class EffectDescription {
		public String category;
		public String[] contents;
		public Ability ability;

		protected EffectDescription(String[] contents) {
			this.contents = Objects.requireNonNull(contents);
			this.category = contents[0];
		}

		public EffectDescription withAbility(Ability ability) {
			this.ability = ability;
			return this;
		}

		public static EffectDescription of(String effectText) {
			return new EffectDescription(effectText.split("/"));
		}
	}

	public static boolean validate(String effectText) {
		final boolean contains = VALID_EFFECT_TEXTS.contains(effectText);
		if (contains) {
			return true;
		}

		final EffectDescription description = EffectDescription.of(effectText);
		final Function<String[], Map<String, ?>> parser = EFFECT_CHECKER.get(description.category);
		if (parser == null) {
			return false;
		}

		return parser.apply(description.contents) != null;
	}

	public static Map<String, ?> parse(EffectDescription description) {
		final Function<String[], Map<String, ?>> parser = EFFECT_CHECKER.get(description.category);
		if (parser == null) {
			throw new RuntimeException("invalid effectText: " + description.category);
		}

		return parser.apply(description.contents);
	}

	/**
	 * 表示某个技能对某个属性的修正值发生改变.
	 * 比如, "fixed/STEEL/1" 表示对 {@link Type#STEEL} 的属性克制修正为 +1
	 */
	private static Map<String, Integer> parseFixedEffect(String[] effectText) {
		Map<String, Integer> results = new HashMap<>();

		Consumer<String> headChecker = Type::valueOf;

		for (int i = 1; i < effectText.length; i += 2) {
			String typeString = effectText[i];
			String valueString = effectText[i + 1];
			headChecker.accept(typeString); // for checking
			results.put(typeString, Integer.valueOf(valueString));
		}

		return results;
	}

	/**
	 * <p>表示某个技能对某个攻击方和防御方有特定 buff 的修正值发生改变. 格式如下:
	 * <pre>
	 * fixed-buff/[attacker-buff],[...more-attacker-buff]/[defense-buff],[...more-defense-buff]/["append" by default or "cover"]/[rate]
	 * </pre>
	 * </p>
	 *
	 * <li>比如, "fixed-buff//flying/cover/1" 解析结果是
	 * <pre>
	 * {
	 *   "attacker-buff":[],         // String[]
	 *   "defense-buff":["flying"],  // String[]
	 *   "type":"cover",             // String
	 *   "rate":1                    // int
	 * }
	 * </pre>
	 * 它表示技能对带有飞行 buff 的敌人效果为 +1 级 (效果拔群), 且计算时忽略任何属性的克制影响 (type=cover)
	 * </li>
	 * <li>比如, "fixed-buff/flying/flying//-1" 解析结果是
	 * <pre>
	 * {
	 *   "attacker-buff":["flying"], // String[]
	 *   "defense-buff":["flying"],  // String[]
	 *   "type":"append",            // String
	 *   "rate":-1                   // int
	 * }
	 * </pre>
	 * 它表示技能如果自己带飞行 buff, 则对带有飞行 buff 的敌人效果为 -1 级 (效果一般),
	 * 计算时将效果追加到判定结果中 (type=append). 追加的意思就是, 如果之前属性判定为效果拔群 (level = 1),
	 * 那这个 buff 的判定 -1 会追加到原来的 level = 1 中, 求和为 0, 表示效果不拔群也不一般.
	 * 任何对 "无效" 判定的追加结果都是无效.
	 * </li>
	 */
	private static Map<String, Object> parseFixedBuffEffect(String[] effectText) {
		if (effectText.length != 5) {
			throw new IllegalArgumentException("invalid move addition: " + Arrays.toString(effectText));
		}

		Map<String, Object> results = new HashMap<>();
		results.put(K_FIXED_BUFF_ATTACKER_BUFFS, "".equals(effectText[1]) ? new String[0] : effectText[1].split(","));
		results.put(K_FIXED_BUFF_DEFENSE_BUFFS, "".equals(effectText[2]) ? new String[0] : effectText[2].split(","));
		results.put(K_FIXED_BUFF_TYPE, "".equals(effectText[3]) ? "append" : effectText[3]);
		results.put(K_FIXED_BUFF_RATE, Integer.valueOf(effectText[4]));

		return results;
	}

	/**
	 * "remove-buff/self/flying"
	 * <pre>
	 * {
	 *   "buffs":["flying"], // String[]
	 *   "target":"self",    // String
	 * }
	 * </pre>
	 */
	private static Map<String, Object> parseRemoveBuffEffect(String[] effectText) {
		if (effectText.length != 3) {
			throw new IllegalArgumentException("invalid move addition: " + Arrays.toString(effectText));
		}

		final String[] split = effectText[2].split(",");
		if (split.length == 0) {
			throw new IllegalArgumentException("invalid move addition: " + Arrays.toString(effectText));
		}

		Map<String, Object> results = new HashMap<>();
		results.put(K_REMOVE_BUFF_BUFFS, split);
		results.put(K_REMOVE_BUFF_TARGET, effectText[1]);

		return results;
	}

	/**
	 * <li>
	 * "apply-buff/self/flying"
	 * <pre>
	 * {
	 *   "target":"self",  // String
	 *   "buff":"flying",  // String
	 *   "properties":[],  // String[]
	 * }
	 * </pre>
	 *
	 * <li>
	 * "apply-buff/enemy/acid/3"
	 * <pre>
	 * {
	 *   "target":"enemy",   // String
	 *   "buff":"acid",      // String
	 *   "properties":["3"], // String[]
	 * }
	 * </pre></li>
	 */
	private static Map<String, Object> parseApplyBuffEffect(String[] effectText) {
		if (effectText.length < 3) {
			throw new IllegalArgumentException("invalid move addition: " + Arrays.toString(effectText));
		}

		Map<String, Object> results = new HashMap<>();
		results.put(K_APPLY_BUFF_TARGET, effectText[1]);
		results.put(K_APPLY_BUFF_BUFF, effectText[2]);
		results.put(K_APPLY_BUFF_PROPERTIES, Arrays.copyOfRange(effectText, 3, effectText.length));

		return results;
	}

	/**
	 * <li>
	 * "b-stat-change/enemy/AT/1"
	 * <pre>
	 * {
	 *   "change":true,      // boolean
	 *   "target":"enemy",   // String
	 *   "AT":1,             // int
	 * }
	 * </pre>
	 *
	 * <li>
	 * "b-stat-change/self/AT/2/SA/2/SP/2/DF/-1/SD/-1"
	 * <pre>
	 * {
	 *   "change":true,      // boolean
	 *   "target":"self",    // String
	 *   "AT":2,             // int
	 *   "SA":2,             // int
	 *   "SP":2,             // int
	 *   "DF":-1,            // int
	 *   "SD":-1,            // int
	 * }
	 *
	 * <li>
	 * "b-stat-set/self/DF/-1"
	 * <pre>
	 * {
	 *   "change":false,     // boolean
	 *   "target":"self",    // String
	 *   "DF":-1,            // int
	 * }
	 * </pre></li>
	 */
	private static Map<String, Object> parseBStatChangeEffect(String[] effectText) {
		if (effectText.length < 4 || effectText.length % 2 == 1) {
			throw new IllegalArgumentException("invalid move addition: " + Arrays.toString(effectText));
		}

		Map<String, Object> results = new HashMap<>();
		results.put(K_BSTAT_CHANGE_CHANGE, E_BSTAT_CHANGE.equals(effectText[0]));
		results.put(K_BSTAT_CHANGE_TARGET, effectText[1]);

		for (int i = 2; i < effectText.length; i += 2) {
			BStat key = BStat.valueOf(effectText[i]);
			int value = Integer.parseInt(effectText[i + 1]);
			results.put(key.name(), value);
		}

		return results;
	}

	private static Map<String, Object> parsePairProperties(String[] effectText) {
		Map<String, Object> results = new HashMap<>();
		for (int i = 1; i < effectText.length; i++) {
			final String[] split = effectText[i].split("=");
			results.put(split[0], split[1]);
		}
		return results;
	}
}
